from OpenGL.GL import *
from PIL import Image  # 导入PIL库，用于加载和处理图像
import numpy as np


def compile_shader(source, shader_type):
    shader = glCreateShader(shader_type)
    glShaderSource(shader, source)
    glCompileShader(shader)

    # Check for compile errors
    success = glGetShaderiv(shader, GL_COMPILE_STATUS)
    if not success:
        info_log = glGetShaderInfoLog(shader).decode()
        raise RuntimeError(f"Shader compilation failed: {info_log}")

    return shader


def create_shader_program(vertex_source, fragment_source):
    vertex_shader = compile_shader(vertex_source, GL_VERTEX_SHADER)
    fragment_shader = compile_shader(fragment_source, GL_FRAGMENT_SHADER)

    program = glCreateProgram()
    glAttachShader(program, vertex_shader)
    glAttachShader(program, fragment_shader)
    glLinkProgram(program)

    # Check for link errors
    success = glGetProgramiv(program, GL_LINK_STATUS)
    if not success:
        info_log = glGetProgramInfoLog(program).decode()
        raise RuntimeError(f"Shader program linking failed: {info_log}")

    glDeleteShader(vertex_shader)
    glDeleteShader(fragment_shader)

    return program


# 加载纹理的函数
def load_texture(filename):
    texture_surface = Image.open(filename).convert("RGBA")  # 打开纹理图像文件
    texture_data = texture_surface.tobytes("raw", "RGBA", 0, -1)  # 将图像数据转换为字节流

    width = texture_surface.width  # 获取图像宽度
    height = texture_surface.height  # 获取图像高度

    texture = glGenTextures(1)  # 生成一个纹理ID

    glBindTexture(GL_TEXTURE_2D, texture)  # 绑定纹理ID到2D纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_data)  # 上传纹理图像数据到OpenGL

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)  # 设置纹理水平方向的环绕方式
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)  # 设置纹理垂直方向的环绕方式
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)  # 设置纹理缩小过滤方式
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)  # 设置纹理放大过滤方式

    return texture  # 返回生成的纹理ID


# 加载天空盒纹理
def load_cubemap(face_paths):
    """加载立方体贴图"""
    texture = glGenTextures(1)
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture)

    for i, face in enumerate(face_paths):
        image = Image.open(face)
        if i == 0 or i == 1 or i == 4 or i == 5:
            image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT).transpose(Image.Transpose.FLIP_TOP_BOTTOM)
        img_data = image.tobytes("raw", "RGBA", 0, -1)  # 将图像数据转换为字节流

        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
                     0, GL_RGBA, image.width, image.height,
                     0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)

    return texture


# 创建深度纹理作为阴影贴图
def create_shadow_texture(width, height):
    texture_id = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, texture_id)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, None)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
    border_color = np.array([1.0, 1.0, 1.0, 1.0], dtype=np.float32)
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color)
    return texture_id


# 创建帧缓冲
def create_shadow_fbo(texture_id, width, height):
    fbo_id = glGenFramebuffers(1)
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture_id, 0)
    glDrawBuffer(GL_NONE)
    glReadBuffer(GL_NONE)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    return fbo_id


class Material:
    def __init__(self, vertex_shader_source, fragment_shader_source):
        self.shader = create_shader_program(vertex_shader_source, fragment_shader_source)


class OpaqueMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source, texture_path, normal_path):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.model_loc = glGetUniformLocation(self.shader, "model")
        self.view_loc = glGetUniformLocation(self.shader, "view")
        self.projection_loc = glGetUniformLocation(self.shader, "projection")
        self.camera_pos_loc = glGetUniformLocation(self.shader, "cameraPos")

        # self.light_space_matrix_loc = glGetUniformLocation(self.shader, "lightSpaceMatrix")

        self.texture = load_texture(texture_path)
        self.normal = load_texture(normal_path)


class SkyboxMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source, face_textures_path, sun_texture_path):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.model_loc = glGetUniformLocation(self.shader, "model")
        self.view_loc = glGetUniformLocation(self.shader, "view")
        self.projection_loc = glGetUniformLocation(self.shader, "projection")
        self.texture = load_cubemap(face_textures_path)
        self.sun_texture = load_texture(sun_texture_path)


class SkyboxMainMenuMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source, face_textures_path):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.model_loc = glGetUniformLocation(self.shader, "model")
        self.view_loc = glGetUniformLocation(self.shader, "view")
        self.projection_loc = glGetUniformLocation(self.shader, "projection")
        self.texture = load_cubemap(face_textures_path)


class CloudsMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source, texture_path):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.model_loc = glGetUniformLocation(self.shader, "model")
        self.view_loc = glGetUniformLocation(self.shader, "view")
        self.projection_loc = glGetUniformLocation(self.shader, "projection")
        self.camera_pos_loc = glGetUniformLocation(self.shader, "cameraPos")
        self.cloud_offset_pos_loc = glGetUniformLocation(self.shader, "cloudOffsetPos")
        self.texture = load_texture(texture_path)


class SelectFrameMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.model_loc = glGetUniformLocation(self.shader, "model")
        self.view_loc = glGetUniformLocation(self.shader, "view")
        self.projection_loc = glGetUniformLocation(self.shader, "projection")
        self.texture = load_texture('Resources/Select_Frame/select_frame.png')


class ShadowMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source, shadow_width=2048, shadow_height=2048):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.model_loc = glGetUniformLocation(self.shader, "model")
        self.light_space_matrix_loc = glGetUniformLocation(self.shader, "lightSpaceMatrix")
        self.shadow_texture = create_shadow_texture(shadow_width, shadow_height)
        self.shadow_fbo = create_shadow_fbo(self.shadow_texture, shadow_width, shadow_height)
        self.shadow_width = shadow_width
        self.shadow_height = shadow_height


class UIMaterial(Material):
    def __init__(self, vertex_shader_source, fragment_shader_source, texture_path):
        super().__init__(vertex_shader_source, fragment_shader_source)
        self.transform_loc = glGetUniformLocation(self.shader, "transform")
        self.tint_loc = glGetUniformLocation(self.shader, "tint")
        self.texture = load_texture(texture_path)
